#ifndef _THREADING_H
#define _THREADING_H

#include "_global.h"

enum WAIT_ID { WAITID_RUN, WAITID_LAST };

// functions
void waitclear();
void wait(WAIT_ID id);
bool waitfor(WAIT_ID id, unsigned int Milliseconds);
void lock(WAIT_ID id);
void unlock(WAIT_ID id);
bool waitislocked(WAIT_ID id);
void waitinitialize();
void waitdeinitialize();

//
// THREAD SYNCHRONIZATION
//
// Win Vista and newer: (Faster) SRW locks used
// Win 2003 and older:  (Slower) Critical sections used
//
#define EXCLUSIVE_ACQUIRE(Index) SectionLocker<Index, false> __ThreadLock
#define EXCLUSIVE_REACQUIRE() __ThreadLock.Lock()
#define EXCLUSIVE_RELEASE() __ThreadLock.Unlock()

#define SHARED_ACQUIRE(Index) SectionLocker<Index, true> __SThreadLock
#define SHARED_REACQUIRE() __SThreadLock.Lock()
#define SHARED_RELEASE() __SThreadLock.Unlock()

enum SectionLock {
  LockMemoryPages,
  LockVariables,
  LockModules,
  LockComments,
  LockLabels,
  LockBookmarks,
  LockFunctions,
  LockLoops,
  LockBreakpoints,
  LockPatches,
  LockThreads,
  LockSym,
  LockCmdLine,
  LockDatabase,
  LockPluginList,
  LockPluginCallbackList,
  LockPluginCommandList,
  LockPluginMenuList,
  LockPluginExprfunctionList,
  LockPluginFormatfunctionList,
  LockSehCache,
  LockMnemonicHelp,
  LockTraceRecord,
  LockCrossReferences,
  LockDebugStartStop,
  LockArguments,
  LockEncodeMaps,
  LockCallstackCache,
  LockRunToUserCode,
  LockWatch,
  LockExpressionFunctions,
  LockHistory,
  LockSymbolCache,
  LockLineCache,
  LockTypeManager,
  LockModuleHashes,
  LockFormatFunctions,
  LockDllBreakpoints,

  // Number of elements in this enumeration. Must always be the last index.
  LockLast
};

class SectionLockerGlobal {
  template <SectionLock LockIndex, bool Shared>
  friend class SectionLocker;

 public:
  static void Initialize();
  static void Deinitialize();

 private:
  static inline void AcquireLock(SectionLock LockIndex, bool Shared) {
    if (m_SRWLocks) {
      if (Shared) {
        if (m_owner[LockIndex].thread == GetCurrentThreadId()) return;

        m_AcquireSRWLockShared(&m_srwLocks[LockIndex]);
        return;
      }

      if (m_owner[LockIndex].thread == GetCurrentThreadId()) {
        assert(m_owner[LockIndex].count > 0);
        m_owner[LockIndex].count++;
        return;
      }

      m_AcquireSRWLockExclusive(&m_srwLocks[LockIndex]);

      assert(m_owner[LockIndex].thread == 0);
      assert(m_owner[LockIndex].count == 0);
      m_owner[LockIndex].thread = GetCurrentThreadId();
      m_owner[LockIndex].count = 1;
    } else
      EnterCriticalSection(&m_crLocks[LockIndex]);
  }

  static inline void ReleaseLock(SectionLock LockIndex, bool Shared) {
    if (m_SRWLocks) {
      if (Shared) {
        if (m_owner[LockIndex].thread == GetCurrentThreadId()) return;

        m_ReleaseSRWLockShared(&m_srwLocks[LockIndex]);
        return;
      }

      assert(m_owner[LockIndex].count && m_owner[LockIndex].thread);
      m_owner[LockIndex].count--;

      if (m_owner[LockIndex].count == 0) {
        m_owner[LockIndex].thread = 0;
        m_ReleaseSRWLockExclusive(&m_srwLocks[LockIndex]);
      }
    } else
      LeaveCriticalSection(&m_crLocks[LockIndex]);
  }

  typedef void(WINAPI* SRWLOCKFUNCTION)(PSRWLOCK SWRLock);

  static bool m_Initialized;
  static bool m_SRWLocks;
  struct owner_info {
    DWORD thread;
    size_t count;
  };
  static owner_info m_owner[SectionLock::LockLast];
  static SRWLOCK m_srwLocks[SectionLock::LockLast];
  static CRITICAL_SECTION m_crLocks[SectionLock::LockLast];
  static SRWLOCKFUNCTION m_InitializeSRWLock;
  static SRWLOCKFUNCTION m_AcquireSRWLockShared;
  static SRWLOCKFUNCTION m_AcquireSRWLockExclusive;
  static SRWLOCKFUNCTION m_ReleaseSRWLockShared;
  static SRWLOCKFUNCTION m_ReleaseSRWLockExclusive;
};

template <SectionLock LockIndex, bool Shared>
class SectionLocker {
 public:
  SectionLocker() {
    m_LockCount = 0;
    Lock();
  }

  ~SectionLocker() {
    if (m_LockCount > 0) Unlock();

    // The lock count should be zero after destruction.
    assert(m_LockCount == 0);
  }

  inline void Lock() {
    Internal::AcquireLock(LockIndex, Shared);

    // We cannot recursively lock more than 255 times.
    assert(m_LockCount < 255);

    m_LockCount++;
  }

  inline void Unlock() {
    // Unlocking twice will cause undefined behaviour.
    assert(m_LockCount != 0);

    m_LockCount--;

    Internal::ReleaseLock(LockIndex, Shared);
  }

 protected:
  BYTE m_LockCount;

 private:
  using Internal = SectionLockerGlobal;
};

#endif  // _THREADING_H
